# -*- coding: utf-8 -*-

import numpy as np
import cv2
from config import project_path
import matplotlib.pyplot as plt

"""
目标
　　• 在这里你将学会怎样读入一幅图像，怎样显示一幅图像，以及如何保存一幅图像
　　• 你将要学习如下函数：cv2.imread()，cv2.imshow()，cv2.imwrite()
　　• 如果你愿意的话，我会叫你如何使用 Matplotlib 显示一幅图片

注意：openCV使用BGR，Matplotlib使用RGB
"""


def read_image():
    """
    使用函数 cv2.imread() 读入图像。这幅图像应该在此程序的工作路径，或者给函数提供完整路径，第二个参数是要告诉函数应该如何读取这幅图片。
　　• cv2.IMREAD_COLOR：读入一副彩色图像。图像的透明度会被忽略，这是默认参数。
　　• cv2.IMREAD_GRAYSCALE：以灰度模式读入图像
　　• cv2.IMREAD_UNCHANGED：读入一幅图像，并且包括图像的 alpha 通道
    """
    img = cv2.imread(project_path + '/data/graph/t1.png', cv2.IMREAD_COLOR)
    print(img)
    return img


def show_image(img):
    """
    使用函数 cv2.imshow() 显示图像。窗口会自动调整为图像大小。第一个参数是窗口的名字，其次才是我们的图像。你可以创建多个窗口，只要你喜欢，但是必须给他们不同的名字
    """
    cv2.imshow('image', img)
    cv2.waitKey(0)
    cv2.destroyAllWindows()


def show_image_plt():
    """
    Matplotlib 是 python 的一个绘图库，里头有各种各样的绘图方法。
    彩色图像使用 OpenCV 加载时是 BGR 模式。但是 Matplotib 是 RGB模式。所以彩色图像如果已经被 OpenCV 读取，那它将不会被 Matplotib 正确显示。
    """
    img = cv2.imread(project_path + '/data/graph/t1.png', 0)
    plt.imshow(img, cmap='gray', interpolation='bicubic')
    plt.xticks([]), plt.yticks([])  # to hide tick values on X and Y axis
    plt.show()


def write_image(img):
    """
    使用函数 cv2.imwrite() 来保存一个图像。首先需要一个文件名，之后才是你要保存的图像。
    """
    cv2.imwrite(project_path + '/data/graph/t3.png', img)


"""
目标
　　• 学习使用 OpenCV 绘制不同几何图形
　　• 你将会学习到这些函数：cv2.line()，cv2.circle()，cv2.rectangle() ，cv2.ellipse() ，cv2.putText() 等

    上面所有的这些绘图函数需要设置下面这些参数：
　　• img：你想要绘制图形的那幅图像。
　　• color：形状的颜色。以 RGB 为例，需要传入一个元组，例如： （255,0,0 ）代表蓝色。对于灰度图只需要传入灰度值。
　　• thickness：线条的粗细。如果给一个闭合图形设置为 -1，那么这个图形就会被填充。默认值是 1.
　　• linetype：线条的类型，8 连接，抗锯齿等。默认情况是 8 连接。cv2.LINE_AA为抗锯齿，这样看起来会非常平滑。
"""


def paint_shape():
    """
    画图形

    要在图片上绘制文字，你需要设置下列参数：
　　• 你要绘制的文字
　　• 你要绘制的位置
　　• 字体类型（通过查看 cv2.putText() 的文档找到支持的字体）
　　• 字体的大小
　　• 文字的一般属性如颜色，粗细，线条的类型等。为了更好看一点推荐使用linetype=cv2.LINE_AA。
    """
    # 创建背景图片
    img = np.zeros((512, 512, 3), np.uint8)
    # 画线
    cv2.line(img, (0, 0), (511, 511), (255, 0, 0), 5)
    # 画矩形
    cv2.rectangle(img, (384, 0), (510, 128), (0, 255, 0), 3)
    # 画圆
    cv2.circle(img, (447, 63), 63, (0, 0, 255), -1)
    # 画椭圆
    cv2.ellipse(img, (256, 256), (100, 50), 0, 0, 180, 255, -1)
    # 画多边形
    pts = np.array([[10, 5], [20, 30], [70, 20], [50, 10]], np.int32)
    pts = pts.reshape((-1, 1, 2))
    cv2.polylines(img, [pts], True, (0, 255, 255))
    # 图片上添加文字
    font = cv2.FONT_HERSHEY_SIMPLEX
    cv2.putText(img, 'OpenCV', (10, 500), font, 4, (255, 255, 255), 2, cv2.LINE_AA)

    win_name = 'example'
    cv2.namedWindow(win_name)
    cv2.imshow(win_name, img)
    cv2.waitKey(0)
    cv2.destroyWindow(win_name)


def operator_image():
    """
    图像操作

        图像属性包括：行，列，通道，图像数据类型，像素数目等
        img.shape：获取图像的形状：行数，列数，通道数的元组, 如果图像是灰度图，返回值仅有行数和列数
        img.size：获取图像的像素数目
        img.dtype：获取图像的数据类型

     图像填充
        在图像周围创建一个边，就像相框一样，使用 cv2.copyMakeBorder()函数。这经常在卷积运算或 0 填充时被用到。这个函数包括如下参数：
        • src 输入图像
        • top, bottom, left, right 对应边界的像素数目。
        • borderType 要添加那种类型的边界，类型如下：
            – cv2.BORDER_CONSTANT 添加有颜色的常数值边界，还需要下一个参数（value）。
            – cv2.BORDER_REFLECT 边界元素的镜像。比如: fedcba|abcde-fgh|hgfedcb
            – cv2.BORDER_REFLECT_101 or cv2.BORDER_DEFAULT跟上面一样，但稍作改动。例如: gfedcb|abcdefgh|gfedcba
            – cv2.BORDER_REPLICATE 重复最后一个元素。例如: aaaaaa|abcdefgh|hhhhhhh
            – cv2.BORDER_WRAP 不知道怎么说了, 就像这样: cdefgh|abcdefgh|abcdefg
        • value 边界颜色，如果边界的类型是 cv2.BORDER_CONSTANT

    """
    img = cv2.imread(project_path + '/data/graph/m3.jpg', cv2.IMREAD_COLOR)

    # 图像属性
    print("图像的形状:")
    print(img.shape)
    print("图像的像素数目:")
    print(img.size)
    print("图像的数据类型:")
    print(img.dtype)

    # ROI(region of interest),感兴趣区域
    ball = img[70:160, 300:400]
    img[20:110, 30:130] = ball
    # show_image(img)

    # 拆分及合并图像通道
    b, g, r = cv2.split(img)
    b = img[:, :, 0]
    g = img[:, :, 1]
    r = img[:, :, 2]
    print(b)
    print(g)
    print(r)
    # show_image(img)

    # 使所有像素的(红色/绿色/蓝色)通道值都为 0，不必先拆分再赋值。可以直接使用 Numpy 索引
    img[:, :, 0] = 0
    show_image(img)

    # 图像扩边/填充
    blue = [255, 0, 0]
    img1 = cv2.imread(project_path + '/data/graph/m3.jpg')
    replicate = cv2.copyMakeBorder(img1, 10, 10, 10, 10, cv2.BORDER_REPLICATE)
    reflect = cv2.copyMakeBorder(img1, 10, 10, 10, 10, cv2.BORDER_REFLECT)
    reflect101 = cv2.copyMakeBorder(img1, 10, 10, 10, 10, cv2.BORDER_REFLECT_101)
    wrap = cv2.copyMakeBorder(img1, 10, 10, 10, 10, cv2.BORDER_WRAP)
    constant = cv2.copyMakeBorder(img1, 10, 10, 10, 10, cv2.BORDER_CONSTANT, value=blue)
    plt.subplot(231), plt.imshow(img1, 'gray'), plt.title('ORIGINAL')
    plt.subplot(232), plt.imshow(replicate, 'gray'), plt.title('REPLICATE')
    plt.subplot(233), plt.imshow(reflect, 'gray'), plt.title('REFLECT')
    plt.subplot(234), plt.imshow(reflect101, 'gray'), plt.title('REFLECT_101')
    plt.subplot(235), plt.imshow(wrap, 'gray'), plt.title('WRAP')
    plt.subplot(236), plt.imshow(constant, 'gray'), plt.title('CONSTANT')
    plt.show()


"""
目标
　　• 学习图像上的算术运算，加法，减法，位运算等。
　　• 我们将要学习的函数与有：cv2.add()，cv2.addWeighted() 等。
"""


def arithmetic_image():
    """
    图像算术运算
        图像加法：
            使用函数 cv2.add() 将两幅图像进行加法运算，当然也可以直接使用 numpy，res=img1+img。
            两幅图像的大小，类型必须一致，或者第二个图像可以使一个简单的标量值。
            注意：OpenCV 中的加法与 Numpy 的加法是有所不同的。OpenCV 的加法是一种饱和操作，而 Numpy 的加法是一种模操作。

        图像混合：
            这其实也是加法，但是不同的是两幅图像的权重不同，这就会给人一种混合或者透明的感觉。图像混合的计算公式如下：
                g (x) = (1 − α)f 0 (x) + αf 1 (x)
            通过修改 α 的值（0 → 1），可以实现非常酷的混合。
            现在我们把两幅图混合在一起。第一幅图的权重是 0.7，第二幅图的权重是 0.3。函数 cv2.addWeighted() 可以按下面的公式对图片进行混合操作。
                dst = α · img1 + β · img2 + γ
            这里 γ 的取值为 0。

        按位运算:
            这里包括的按位操作有：AND，OR，NOT，XOR 等。
            当我们提取图像的一部分，选择非矩形 ROI 时这些操作会很有用（下一章你就会明白）。下面的例子就是教给我们如何改变一幅图的特定区域。
            我想把 OpenCV 的标志放到另一幅图像上。如果我使用加法，颜色会改变，如果使用混合，会得到透明效果，但是我不想要透明。
            如果他是矩形我可以象上一章那样使用 ROI。但是他不是矩形。但是我们可以通过下面的按位运算实现：
    """
    # 图像加法
    x = np.uint8([250])
    y = np.uint8([10])
    print(cv2.add(x, y))  # 250+10 = 260 => 255
    print(x + y)  # 250+10 = 260 % 256 = 4

    # 图像混合
    img1 = cv2.imread(project_path + '/data/graph/m3.jpg')
    img2 = cv2.imread(project_path + '/data/graph/m3.jpg')
    img1[:, :, 1] = 160
    img2[:, :, 0] = 100
    dst = cv2.addWeighted(img1, 0.7, img2, 0.8, 0)
    # show_image(dst)

    # 按位运算
    img1 = cv2.imread(project_path + '/data/graph/m3.jpg')
    img2 = cv2.imread(project_path + '/data/graph/t1.png')

    # I want to put logo on top-left corner, So I create a ROI
    rows, cols, channels = img2.shape
    roi = img1[0:rows, 0:cols]

    # Now create a mask of logo and create its inverse mask also
    img2gray = cv2.cvtColor(img2, cv2.COLOR_BGR2GRAY)
    ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
    mask_inv = cv2.bitwise_not(mask)

    # Now black-out the area of logo in ROI
    img1_bg = cv2.bitwise_and(roi, roi, mask=mask_inv)

    # Take only region of logo from logo image.
    img2_fg = cv2.bitwise_and(img2, img2, mask=mask)

    # Put logo in ROI and modify the main image
    dst = cv2.add(img1_bg, img2_fg)
    img1[0:rows, 0:cols] = dst

    show_image(img1)


if __name__ == '__main__':
    # # 读取图像
    # g_img = read_image()
    # # 显示图像
    # show_image(g_img)
    # show_image_plt()
    # # 保存图像
    # write_image(g_img)
    # # 绘制图像
    # paint_shape()
    # # 图像基本操作
    # operator_image()

    # 图像算术运算
    arithmetic_image()
